﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Windows.Forms;
using LumenWorks.Framework.IO.Csv;
using SdmxMl.Structure;
using SdmxMl.Manager;

namespace SdmxMl.Data
{
    public class CsvMetaCheck
    {
        private string errorMessage;
        private CsvMetaHeaderCol currentMetaInfo;

        public int ErrorCount { get; private set; }
        public DataTable ErrorTable { get; private set; }
        public Dictionary<int, int> ErrorCountByColumn { get; private set; }

        public int MaxErrPerCol { get; set; }
        public int MaxErrTotal { get; set; }

        private DicoAlreadyChecked[] arrayDicoAlreadyChecked;
        

        private CsvDataHeader metaDataHeader;

        public CsvMetaCheck(CsvDataHeader metaHeader)
        {
            metaDataHeader = metaHeader;
            ErrorCountByColumn = new Dictionary<int,int>(metaHeader.DicoHeadCol.Count);
            ErrorTable = GenErrorDataTable();

            arrayDicoAlreadyChecked = new DicoAlreadyChecked[metaHeader.DicoHeadCol.Count];
            for (int i = 0; i < metaHeader.DicoHeadCol.Count; ++i)
                arrayDicoAlreadyChecked[i] = new DicoAlreadyChecked(32);

            int metaIndex = 0;
            foreach (KeyValuePair<int, CsvMetaHeaderCol> pair in metaDataHeader.DicoHeadCol)
            {
                // Error counters by column
                if (ErrorCountByColumn.ContainsKey(metaIndex) == false)
                    ErrorCountByColumn.Add(metaIndex, 0);

                ++metaIndex;
            }

        }

        public void PersistErrorTable(string csvPath)
        {
            DataSet ds = new DataSet();
            ds.Tables.Add(ErrorTable);
            ds.WriteXml(CsvChuncher.GetChunkInfoErrorPath(csvPath), XmlWriteMode.WriteSchema);
        }

        private DataTable GenErrorDataTable()
        {
            DataTable dtVar = new DataTable("Errors");
            dtVar.Columns.Add(new DataColumn("Var", typeof(string)));
            dtVar.Columns.Add(new DataColumn("Error", typeof(string)));
            dtVar.Columns.Add(new DataColumn("Chunk", typeof(int)));
            dtVar.Columns.Add(new DataColumn("Row", typeof(int)));
            dtVar.Columns.Add(new DataColumn("Col", typeof(int)));
            dtVar.Columns.Add(new DataColumn("Msg", typeof(string)));
          //  dtVar.PrimaryKey = new DataColumn[] { dtVar.Columns[0] };

            return dtVar;
        }

        public bool Check(DataGridView dataGrid)
        {
            ErrorTable = GenErrorDataTable();
 
            // Foe each mapped column, for each row check
            foreach (KeyValuePair<int, CsvMetaHeaderCol> pair in  metaDataHeader.DicoHeadCol)
            {
                currentMetaInfo = pair.Value;
                int colError = 0;
                Dictionary<string, string> dicoAlreadyChecked = new Dictionary<string, string>(32);
                for (int row = 0; row < dataGrid.RowCount && colError < MaxErrPerCol; ++row)
                {
                    string val = dataGrid[pair.Key, row].Value.ToString();
                    errorMessage = string.Empty;
                    if (val.Length > 0)
                    {
                        
                        if (dicoAlreadyChecked.ContainsKey(val))
                        {
                            errorMessage = dicoAlreadyChecked[val];
                            if (errorMessage.Length > 0)
                            {
                                ErrorTable.Rows.Add(new object[] 
                                {
                                    pair.Value.Concept.Id,
                                    "Code",
                                    0,
                                    row,
                                    pair.Key,
                                    errorMessage
                                });
                                // Check limit in error control
                                if (++colError > MaxErrPerCol)
                                    break;
                            }
                        }
                        else
                         
                        {
                            if (currentMetaInfo.Codelist != null)
                            {
                                if (CheckCodeValue(val))
                                {
                                    // Create error record
                                    ErrorTable.Rows.Add(new object[] 
                                {
                                    pair.Value.Concept.Id,
                                    "Code",
                                    0,
                                    row,
                                    pair.Key,
                                    errorMessage
                                });

                                    // Check limit in error control
                                    if (++colError > MaxErrPerCol)
                                        break;
                                }
                                dicoAlreadyChecked.Add(val, errorMessage);
                            }
                            else
                            {
                                // Quality code but no main codelist ?
                                if (IsQualityCode(val) == false)
                                {
                                    // TextFormat checkings
                                    
                                }
                                else
                                {
                                    dicoAlreadyChecked.Add(val, errorMessage);
                                }

                                if (errorMessage.Length > 0)
                                {
                                    // Create error record
                                    ErrorTable.Rows.Add(new object[] 
                                {
                                    pair.Value.Concept.Id,
                                    "Code",
                                    0,
                                    row,
                                    pair.Key,
                                    errorMessage
                                });

                                    // Check limit in error control
                                    if (++colError > MaxErrPerCol)
                                        break;
                                }
                            }
                        }
                    }
                }
                ErrorCountByColumn.Add(pair.Key, colError);
                ErrorCount += colError;

                if (ErrorCount > MaxErrTotal)
                    break;
            }

            return ErrorCount > 0;
        }

        /// <summary>Check for a valid code or quality code </summary>
        /// <param name="val">code value to check</param>
        /// <returns>true on error</returns>
        /// <remarks>Codelist existence must be ensured</remarks>
        private bool CheckCodeValue(string val)
        {
            bool err = false;
            CodeType c = currentMetaInfo.Codelist.CodeList.LocateItem(val) as CodeType;
            if (c == null)
            {
                if (currentMetaInfo.CodeQuality == null)
                {
                    err = true;
                    errorMessage = string.Format("Undefined code: {0}", val);
                }
                else
                {
                    c = currentMetaInfo.CodeQuality.CodeList.LocateItem(val) as CodeType;
                    if (c != null && currentMetaInfo.QualitySubset != null)
                    {
                        if (currentMetaInfo.QualitySubset.Contains(val) == false)
                        {
                            err = true;
                            errorMessage = string.Format("Quality code: {0} not in defined subset.", val);
                        }
                    }

                    if (c == null)
                    {
                        err = true;
                        errorMessage = string.Format("Undefined code: {0}", val);
                    }
                }
            }
            return err;
        }
        /// <summary>
        /// Quality code can exist without principal codelist (we do check only existance)
        /// </summary>
        /// <returns>true if val in quality list</returns>
        public bool IsQualityCode(string val)
        {
            if (currentMetaInfo.CodeQuality != null)
            {
                CodeType c = currentMetaInfo.CodeQuality.CodeList.LocateItem(val) as CodeType;
                if (c != null && currentMetaInfo.QualitySubset != null)
                {
                    if (currentMetaInfo.QualitySubset.Contains(val) == false)
                        errorMessage = string.Format("Quality code: {0} not in defined subset.", val);
                }

                return c != null;
            }

            return false;
        }



        public bool CheckChunk(CachedCsvReader csv, CsvChunk currentChunk)
        {
            

            // For each mapped column, for each row check
            int row = currentChunk.RowStart;
            while (csv.ReadNextRecord())
            {
                int metaIndex = 0;
                foreach (KeyValuePair<int, CsvMetaHeaderCol> pair in metaDataHeader.DicoHeadCol)
                {
                    currentMetaInfo = pair.Value;
                    string val = csv[pair.Key];
                    errorMessage = string.Empty;

                    // some value to check ?
                    if (val.Length > 0)
                    {
                        // Already a diagnostic for this value ?
                        if (arrayDicoAlreadyChecked[metaIndex].ContainsKey(val))
                        {
                            errorMessage = arrayDicoAlreadyChecked[metaIndex][val];
                            if (errorMessage.Length > 0)
                            {
                                ErrorTable.Rows.Add(new object[] 
                                {
                                    pair.Value.Concept.Id,
                                    "Code",
                                    currentChunk.ChunkId,
                                    row,
                                    pair.Key,
                                    errorMessage
                                });
                                ++ErrorCountByColumn[metaIndex];
                                ++ErrorCount;
                            }
                        }
                        else  // new value to diagnosticate
                        {
                            if (currentMetaInfo.Codelist != null)
                            {
                                if (CheckCodeValue(val))
                                {
                                    // Create error record
                                    ErrorTable.Rows.Add(new object[] 
                                    {
                                        pair.Value.Concept.Id,
                                        "Code",
                                        currentChunk.ChunkId,
                                        row,
                                        pair.Key,
                                        errorMessage
                                    });

                                    ++ErrorCountByColumn[metaIndex];
                                    ++ErrorCount;
                                }
                                // Store diagnostic
                                arrayDicoAlreadyChecked[metaIndex].Add(val, errorMessage);
                            }
                            else
                            {
                                // Quality code but no main codelist ?
                                if (IsQualityCode(val) == false)
                                {
                                    if (currentMetaInfo.TextFormat.TextType == TextTypeType.Integer ||
                                        currentMetaInfo.TextFormat.TextType == TextTypeType.Month ||
                                        currentMetaInfo.TextFormat.TextType == TextTypeType.Day ||
                                        currentMetaInfo.TextFormat.TextType == TextTypeType.Year
                                        )
                                    {
                                        int r = 0;
                                        if (int.TryParse(val, out r) == false)
                                        {
                                            errorMessage = "Invalid Integer value : " + val;
                                        }
                                        else if (currentMetaInfo.TextFormat.TextType == TextTypeType.Month && 
                                            (r < 1 || r > 12))
                                        {
                                               errorMessage = "Invalid Month value : " + val;
                                        }
                                        else if (currentMetaInfo.TextFormat.TextType == TextTypeType.Day &&
                                            (r < 1 || r > 31))
                                        {
                                            errorMessage = "Invalid Day value : " + val;
                                        }
                                        else if (currentMetaInfo.TextFormat.TextType == TextTypeType.Year &&
                                            (r < 1000 || r > 9999))
                                        {
                                            errorMessage = "Invalid Year, not a four dight : " + val;
                                        }
                                    }
                                    // TextFormat checkings
                                    if (string.IsNullOrEmpty(currentMetaInfo.TextFormat.maxLength) == false)
                                    {
                                        int xxx;
                                        if (int.TryParse(currentMetaInfo.TextFormat.maxLength, out xxx))
                                        {
                                            if (val.Length > xxx)
                                                errorMessage = "Invalid Length, too many chars.. " + val;
                                        }
                                        if (int.TryParse(currentMetaInfo.TextFormat.minLength, out xxx))
                                        {
                                            if (val.Length < xxx)
                                                errorMessage = "Invalid Length, too few chars.. " + val;
                                        }
                                    }

                                }
                                else
                                {
                                    arrayDicoAlreadyChecked[metaIndex].Add(val, errorMessage);
                                }

                                if (errorMessage.Length > 0)
                                {
                                    // Create error record
                                    ErrorTable.Rows.Add(new object[] 
                                    {
                                        pair.Value.Concept.Id,
                                        "Code",
                                        currentChunk.ChunkId,
                                        row,
                                        pair.Key,
                                        errorMessage
                                    });

                                    ++ErrorCountByColumn[metaIndex];
                                    ++ErrorCount;
                                }
                            }
                        }
                    }
                    
                    ++metaIndex;
                } // foreach
                if (ErrorCount > MaxErrTotal)
                    break;
                ++row;
            }
            
            return ErrorCount > 0;
        }
    }

    public class DicoAlreadyChecked : Dictionary<string, string>
    {
        public DicoAlreadyChecked(int capacity) : base(capacity) { }
    }
}
